package com.juc.concurrency.jucExample.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author huangweiyue
 * @description 有界队列BoundedBuffer来了解Condition的使用方式，该类是一个特殊的队列，
 * 当队列为空时，队列的获取操作将会阻塞当前"拿"线程，直到队列中有新增的元素，
 * 当队列已满时，队列的放入操作将会阻塞"放入"线程，直到队列中出现空位。
 * ====================================================================================
 * 代码中我们可以看出，在该类中我们创建了两个等待队列notFull与notEmpty。
 * 这两个等待队列的作用分别是，当请数组已满时，notFull用于存储阻塞的"放入"线程，notEmpty用于存储阻塞的"拿"线程。
 * 需要注意的是获取一个Condition必须通过Lock的newCondition()方法
 * @date Created in 2020-04-22
 * @see ArrayBlockingQueue.java 实现阻塞队列的方式就是这种
 */
public class ConditionBoundedBuffer {
    final Lock lock = new ReentrantLock();
    //放 即没有满notFull
    final Condition putCondition = lock.newCondition();
    //拿 即不为空notEmpty
    final Condition takeCondition = lock.newCondition();

    final Object[] items = new Object[100];

    /**
     * 放入的角标
     */
    private int putptr;
    /**
     * 拿的角标
     */
    private int takeptr;
    /**
     * 数组中放入的对象总数
     */
    private int count;

    /**
     * 添加一个元素
     * （1）如果当前数组已满，则把当前"放入"线程，加入到"放入"等待队列中，并阻塞当前线程
     * （2）如果当前数组未满，则将x元素放入数组中，唤醒"拿"线程中的等待线程。
     */
    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)//如果已满，则阻塞当前"放入"线程
            {
                putCondition.await();
            }
            items[putptr] = x;
            if (++putptr == items.length) {
                putptr = 0;
            }
            ++count;
            takeCondition.signal();//唤醒"拿"线程
        } finally {
            lock.unlock();
        }
    }

    /**
     * 拿一个元素
     * （1）如果当前数组已空，则把当前"拿"线程，加入到"拿"等待队列中，并阻塞当前线程
     * （2）如果当前数组不为空，则把唤醒"放入"等待队列中的线程。
     */
    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)//如果为空，则阻塞当前"拿"线程
            {
                takeCondition.await();
            }
            Object x = items[takeptr];
            if (++takeptr == items.length) {
                takeptr = 0;
            }
            --count;
            putCondition.signal();//唤醒"放入"线程
            return x;
        } finally {
            lock.unlock();
        }
    }
}

